# mysql模块
30 秒速记
mysql包是Node连MySQL的驱动,回调风格;新项目一般直接用mysql2,自带PromiseAPIMySQL 8默认认证插件是caching_sha2_password,老mysql包连不上,报ER_NOT_SUPPORTED_AUTH_MODE- 线上用连接池
createPool,控制connectionLimit,用完一定release - 值一律用
?占位符传参,别拼字符串;表名、排序字段用白名单 - 进程退出前
pool.end(),不然连接挂着,脚本不退出
mysql 模块就是 Node 和 MySQL 之间的翻译官,帮你建连接、发 SQL、把结果变成 JS 对象。 它是纯 JS 写的、回调风格,教程里很常见,但现在新项目我一般选 mysql2:API 基本兼容,多了 mysql2/promise 可以直接 await,还支持 MySQL 8 默认的 caching_sha2_password 认证。不管用哪个,三件事是固定的:用连接池、用占位符防注入、用完把连接还回去。
理解「mysql模块」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 database 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
Node 里操作 MySQL,本质就是三步:拿到一个连接,发一条 SQL,拿结果。下面用 mysql2/promise 写一个最小可运行的例子,概念和 mysql 包是一样的,只是不用写回调。
// npm i mysql2
const mysql = require('mysql2/promise')
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: process.env.DB_PASSWORD, // 密码别写死在代码里
database: 'koa_demo',
connectionLimit: 10,
})
async function main() {
// pool.query 会自动 getConnection + release
const [rows] = await pool.query('SELECT id, email FROM user WHERE id = ?', [1])
console.log(rows) // [ { id: 1, email: '1@example.com' } ]
await pool.end() // 关掉连接池,进程才会退出
}
main().catch(err => {
console.error(err.code, err.message) // 比如 ER_ACCESS_DENIED_ERROR
process.exit(1)
})
几个容易踩的点:
pool.query是getConnection+query+release的简写,绝大多数查询用它就够了。只有事务这种「多条语句必须在同一个连接上执行」的场景,才需要手动getConnection,并在finally里release。- 错误要按
err.code区分:ECONNREFUSED是库没起来,ER_ACCESS_DENIED_ERROR是账号密码错,ER_DUP_ENTRY是唯一键冲突。只有连接类错误值得重试,唯一键冲突重试多少次都一样。 - 时间字段建议用
DATETIME或TIMESTAMP,字符集用utf8mb4,否则存emoji会报错。
面试官追问
追问 1本地装的 MySQL 8,用 mysql 包一连就报 ER_NOT_SUPPORTED_AUTH_MODE,怎么办?
老 mysql 包不支持 MySQL 8 默认的 caching_sha2_password。最干净的办法是换 mysql2;实在不能换,就把账号改成 mysql_native_password,但这是在降安全性,只适合本地。
追问 2'SELECT * FROM user WHERE id = ' + id 这么写有什么问题?
id 传个 1 OR 1=1 就把整张表查出来了,这是 SQL 注入。改成 query('SELECT * FROM user WHERE id = ?', [id]),驱动会负责转义。
追问 3排序字段是前端传的 orderBy,能放进 ? 占位符吗?
不行,? 会把它当字符串值转义成 'name',排序就失效了。标识符要走白名单:const col = ['name', 'created_at'].includes(orderBy) ? orderBy : 'id',或者用 ?? 占位符做标识符转义。
追问 4脚本查完数据打印了结果,但进程就是不退出,为什么?
连接池或连接还开着,事件循环里还有活跃的 socket。脚本最后调一次 pool.end() 或 connection.end(),进程就会自然退出。
追问 5mysql 和 mysql2 怎么选?
新项目选 mysql2:维护活跃、有 Promise API、支持预处理语句 execute、性能也更好。老项目已经稳定跑着 mysql,没有遇到认证或性能问题,不必为了换而换。
# mysql模块
# 快速开始
30 秒速记
- 三步走:装
MySQL服务 →npm i mysql(或mysql2)→createConnection/createPool发SQL - 先在命令行用
mysql -u root -p确认库能连上,再写Node代码,排错能少一半 - 连接配置至少要有
host、user、password、database - 密码放环境变量,别写死在代码里提交上去
快速开始就是把「数据库能连上、Node 能发 SQL、能拿到结果」这条链路跑通。 我一般先不碰代码,用 mysql -u root -p 登进去,CREATE DATABASE 建好库,确认账号密码没问题;然后再装驱动写最小的 SELECT 1。这样一旦报错,就能分清是数据库本身的问题还是 Node 代码的问题。跑通后第一件事就是把密码挪到 .env 里。
理解「快速开始」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
面试官追问
追问 1Node 报 ECONNREFUSED 127.0.0.1:3306,第一步查什么?
先确认 MySQL 服务有没有起来、端口是不是 3306,命令行 mysql -h 127.0.0.1 -u root -p 能不能连上。命令行都连不上,就不用去看 Node 代码了。
追问 2命令行能连上,Node 报 ER_ACCESS_DENIED_ERROR,为什么?
账号密码或者授权的 host 不对。比如账号只授权了 'root'@'localhost',而 Node 用 127.0.0.1 走 TCP 连,MySQL 会把它当成不同的来源。
追问 3最小的连通性测试你会写什么?
await pool.query('SELECT 1'),能返回就说明网络、账号、库都通了。服务启动时跑一次这个,失败直接退出,比等到第一个用户请求才报错强。
追问 4用 Docker 起 MySQL,Node 也在容器里,host 填 127.0.0.1 连不上?
容器里的 127.0.0.1 是它自己。同一个 docker compose 网络里,host 要填服务名,比如 db。
# 安装MySQL数据库
30 秒速记
- 本地开发三种装法:官网安装包 /
brew install mysql/docker run mysql:8,我更推荐Docker,删了不留垃圾 - 装完先改
root密码,再建一个业务专用账号,别让应用直接用root MySQL 8默认认证caching_sha2_password,老驱动会连不上- 字符集设成
utf8mb4,utf8在MySQL里只有3字节,存不了emoji
本地装 MySQL 只是为了有个能连的库,怎么省事怎么来,我一般直接用 Docker。 一条 docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8 就起来了,不想要了删容器就行。装完要注意两件事:一是 MySQL 8 默认的认证方式,老的 mysql 包不支持,要么换 mysql2,要么给账号改认证插件;二是建库时指定 utf8mb4,不然用户昵称里带个 emoji 就插入失败。
https://www.mysql.com/downloads/
理解「安装MySQL数据库」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
